package com.lhz.project.system.service.base.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lhz.common.constant.DataStatusEnum;
import com.lhz.common.constant.ResponseState;
import com.lhz.common.utils.UUIDUtils;
import com.lhz.common.utils.file.FileTypeUtil;
import com.lhz.common.utils.file.FileUtil;
import com.lhz.framework.exception.ServiceException;
import com.lhz.project.system.mapper.base.FileUploadMapper;
import com.lhz.project.system.model.entity.base.FileUpload;
import com.lhz.project.system.model.vo.base.FileDownloadVo;
import com.lhz.project.system.model.vo.base.FileUploadVo;
import com.lhz.project.system.service.base.FileUploadService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.*;


/**
 * @author lihuazhi
 */
@Service
@Slf4j
public class FileUploadServiceImpl extends ServiceImpl<FileUploadMapper, FileUpload> implements FileUploadService {
    @Value("${server.servlet.context-path}")
    private String contextPath;

    // 文件存储的根路径
    @Value("${sys.file.root-path}")
    private String rootPath;

    //文件大小限制
    @Value("${sys.file.max-size}")
    private Long maxSize;

    //文件格式限制
    @Value("${sys.file.format}")
    private String format;

    @Resource
    private HttpServletResponse response;

    @Resource
    private HttpServletRequest request;

    @Resource
    private FileUploadMapper fileUploadMapper;

    /**
     * @param files 文件
     * @return
     */
    @Override
    public Object uploadFile(String dataId, List<MultipartFile> files) {

        Assert.notEmpty(files, ResponseState.FILE_NOT_EMPTY.name());

        int baseOrderNum = 1;
        //查询业务有已存在文件，并且获取最大序号
        FileUpload upload = fileUploadMapper.selectMaxNumFile(dataId);
        if (upload != null) {
            baseOrderNum = upload.getOrderNum() + 1;
        }
        List<FileUploadVo> voList = new ArrayList<>();
        List<FileUpload> uploadList = new ArrayList<>();
        Date date = new Date();
        for (MultipartFile file : files) {
            // 获取文件名
            String fileName = file.getOriginalFilename();
            Assert.notBlank(fileName, ResponseState.FILE_NAME_NOT_EMPTY.name());

            log.info("上传的文件名为：" + fileName);

            // 获取文件的后缀名
            String suffixName = StrUtil.subAfter(fileName, ".", true);
            if (StrUtil.isEmpty(suffixName)) {
                suffixName = "null";
            }
            log.info("文件的后缀名为：" + suffixName);

            //获取系统配置格式
            if (!StrUtil.isBlank(format)) {
                String[] fileSuffix = format.split(",");
                boolean contains = Arrays.asList(fileSuffix).contains(suffixName.toLowerCase());
                Assert.isTrue(contains, "上传文件类型不支持，仅支持：" + format);
            }

            //获取文件大小
            long fileSize = file.getSize();
            log.info("文件的大小为：" + fileSize);
            long max = maxSize * 1024 * 1024; //默认将M转换为B
            Assert.isFalse(fileSize > max && maxSize > 0, "请上传小于" + maxSize + "MB的文件");

            // 存储文件名
            String saveName = UUIDUtils.getUuId() + "." + suffixName;
            int hashCode = fileName.hashCode();
            String hex = Integer.toHexString(hashCode);

            // 文件保存路径
            String savePath = "/source/" + suffixName.toLowerCase() + "/" + hex.charAt(0) + "/" + hex.charAt(1) + "/";

            // 创建文件对象
            File dirFile = new File(rootPath, savePath);
            if (!dirFile.exists()) {
                boolean makeDirs = dirFile.mkdirs();
                Assert.isTrue(makeDirs, ResponseState.FILE_UPLOAD_FALL.name());
            }

            // 保存文件
            File saveFile = new File(dirFile, saveName);
            try {
                file.transferTo(saveFile);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            //获取属性
            String fileId = UUIDUtils.getUuId();
            String url = savePath + saveName;
            String format = FileTypeUtil.getFormat(saveName);

            // 记录上传日志
            FileUpload fileUpload = new FileUpload();
            fileUpload.setId(fileId);
            fileUpload.setDataId(dataId);
            fileUpload.setOrderNum(baseOrderNum);
            fileUpload.setName(fileName);

            fileUpload.setUrl(savePath + saveName);
            fileUpload.setSize(fileSize);
            fileUpload.setFormat(format);
            fileUpload.setStatus(DataStatusEnum.VALID.getCode());
            fileUpload.setDelFlag(DataStatusEnum.VALID.getCode());
            uploadList.add(fileUpload);

            //返回对象
            FileUploadVo uploadVo = new FileUploadVo();
            uploadVo.setFileId(fileId);
            uploadVo.setFileName(fileName);
            uploadVo.setSize(fileSize);
            uploadVo.setFormat(format);

            //此处直接返回文件上传后在服务器的储存位置，前端访问分两个情况：
            //1、通过SpringBoot配置的静态资源访问，前端只需要网关IP:Port + 返回地址即可(url)
            //2、通过配置Nginx访问，前端只需要Nginx文件地址 + 返回地址即可(url)

            //TODO 此处没有配置nginx，通过静态资源访问，需要单独加上当前项目的Path
            uploadVo.setUrl(contextPath + url);
            voList.add(uploadVo);
        }
        //插入数据库
        if (!uploadList.isEmpty()) {
            saveBatch(uploadList);
        }

        return voList;
    }


    /**
     * 删除已上传文件
     *
     * @return
     */
    @Override
    public void deleteUploadFile(String fileId) {
        // 查询记录
        FileUpload fileUpload = fileUploadMapper.selectByPrimaryKey(fileId);
        Assert.notNull(fileUpload, "文件不存在");
        fileUploadMapper.deleteById(fileId);
        //删除服务器文件
        FileUtil.delFile(rootPath + fileUpload.getUrl());
    }

    @Override
    public void deleteUploadFileByData(String dataId) {
        // 查询记录
        List<FileUpload> uploadList = fileUploadMapper.listFileUploadByDataId(dataId);
        Assert.notEmpty(uploadList, "文件不存在");
        fileUploadMapper.deleteRealByDataId(dataId);
        //删除服务器文件
        for (FileUpload fileUpload : uploadList) {
            FileUtil.delFile(rootPath + fileUpload.getUrl());
        }
    }

    /**
     * 获取业务文件列表
     *
     * @param dataId
     * @return
     */
    @Override
    public Object getFileListByData(String dataId) {
        List<FileUpload> uploadList = fileUploadMapper.listFileUploadByDataId(dataId);
        List<FileUploadVo> uploadVoList = new ArrayList<>();
        for (FileUpload fileUpload : uploadList) {
            FileUploadVo uploadVo = new FileUploadVo();
            uploadVo.setFileId(fileUpload.getId());
            uploadVo.setFormat(fileUpload.getFormat());
            uploadVo.setSize(fileUpload.getSize());
            uploadVo.setFileName(fileUpload.getName());

            //此处直接返回文件上传后在服务器的储存位置，前端访问分两个情况：
            //1、通过SpringBoot配置的静态资源访问，前端只需要网关IP:Port + 返回地址即可(url)
            //2、通过配置Nginx访问，前端只需要Nginx文件地址 + 返回地址即可(url)

            //TODO 此处没有配置nginx，通过静态资源访问，需要单独加上当前项目的Path
            uploadVo.setUrl(contextPath + fileUpload.getUrl());
            uploadVoList.add(uploadVo);
        }
        return uploadVoList;
    }

    /**
     * 下载文件
     *
     * @param fileId
     */
    @Override
    public void downloadFileStream(String fileId) {
        FileUpload fileUpload = fileUploadMapper.selectByPrimaryKey(fileId);
        Assert.notNull(fileUpload, "文件不存在");
        FileUtil.downloadFile(new File(rootPath + fileUpload.getUrl()), request, response, fileUpload.getName());
    }

    /**
     * 批量下载文件(zip流)
     *
     * @return
     */
    @Override
    public void downloadFileByDataStream(String dataId) {
        List<FileUpload> uploadList = fileUploadMapper.listFileUploadByDataId(dataId);
        Assert.notEmpty(uploadList, "文件不存在");
        List<Map<String, String>> mapList = new ArrayList<>();
        for (FileUpload upload : uploadList) {
            Map<String, String> map = new HashMap<>();
            map.put("path", rootPath + upload.getUrl());
            map.put("name", upload.getName());
            mapList.add(map);
        }
        FileUtil.zipDirFileToFile(mapList, request, response);
    }

    /**
     * 下载文件
     *
     * @param fileId
     */
    @Override
    public Object downloadFile(String fileId) {
        FileUpload fileUpload = fileUploadMapper.selectByPrimaryKey(fileId);
        Assert.notNull(fileUpload, "文件不存在");
        //封装对象
        FileDownloadVo downloadVo = new FileDownloadVo();
        //设置文件源名称，由前端对下载文件进行重命名
        downloadVo.setFileName(fileUpload.getName());
        //TODO 此处没有配置nginx，通过静态资源访问，需要单独加上当前项目的Path
        downloadVo.setUrl(contextPath + fileUpload.getUrl());

        //此处直接返回文件上传后在服务器的储存位置，前端访问分两个情况：
        //1、通过SpringBoot配置的静态资源访问，前端只需要网关IP:Port+ 返回地址即可
        //2、通过配置Nginx访问，前端只需要Nginx文件地址 + 返回地址即可
        return downloadVo;
    }

    /**
     * 批量下载文件(文件地址)
     *
     * @return
     */
    @Override
    public Object downloadFileByData(String dataId) {
        List<FileUpload> uploadList = fileUploadMapper.listFileUploadByDataId(dataId);
        Assert.notEmpty(uploadList, "文件不存在");
        List<Map<String, String>> mapList = new ArrayList<>();
        for (FileUpload upload : uploadList) {
            Map<String, String> map = new HashMap<>();
            map.put("path", rootPath + upload.getUrl());
            map.put("name", upload.getName());
            mapList.add(map);
        }

        //取压缩包中第一个文件名称作为zip名称
        String fileName = uploadList.get(0).getName();
        String zipName = fileName.substring(0, fileName.lastIndexOf("."));
        String saveName = zipName + "等" + ".zip";

        int hashCode = saveName.hashCode();
        String hex = Integer.toHexString(hashCode);
        // 文件保存路径
        String savePath = "/source/zip" + "/" + hex.charAt(0) + "/" + hex.charAt(1) + "/";
        try {
            // 存储文件
            FileUtil.zipDirFileToFile(mapList, rootPath + savePath, saveName);
        } catch (Exception e) {
            throw new ServiceException("下载失败!");
        }
        //封装对象
        FileDownloadVo downloadVo = new FileDownloadVo();
        //设置文件源名称，由前端对下载文件进行重命名
        downloadVo.setFileName(saveName);
        //TODO 此处没有配置nginx，通过静态资源访问，需要单独加上当前项目的Path
        downloadVo.setUrl(contextPath + savePath + saveName);

        //此处直接返回文件上传后在服务器的储存位置，前端访问分两个情况：
        //1、通过SpringBoot配置的静态资源访问，前端只需要网关IP:Port + 返回地址即可
        //2、通过配置Nginx访问，前端只需要Nginx文件地址 + 返回地址即可
        return downloadVo;
    }
}
